package evs.location;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.hibernate.SessionFactory;

public abstract class AbstractRequestHandler
{
    public enum MediaType
    {
        XML,
        JSON
    }
    
    protected final String _entityId;
    protected final SessionFactory _sessionFactory;
    protected final EntityMap _entityMap;
    protected final MediaType _mediaType;
    protected final HttpServletRequest _request;
    
    private final ArrayList<Class<?>> _requestInterceptorClasses;
    private ArrayList<RequestInterceptor> _requestInterceptors;
    
    private Class<?> _expectedEntityClass;
    
    public AbstractRequestHandler( 
                    HttpServletRequest request, 
                    SessionFactory sessionFactory, 
                    List<Class<?>> interceptorClasses, 
                    EntityMap entityMap ) 
        throws EvsException
    {
        _request = request;
        _sessionFactory = sessionFactory;
        _entityMap = entityMap;
        
        _requestInterceptorClasses = new ArrayList<Class<?>>( interceptorClasses );
        _runBeforeInterceptors( request );
        
        String requestPath = extractPath( request );
        _entityId = _extractEntityId( requestPath );
        
        _mediaType = _extractMediaType( request );
    }
    
    protected abstract boolean allowUnknownMediaType();
    protected abstract void _execute( HttpServletResponse response ) throws EvsException;
    
    
    public void execute( HttpServletResponse response ) throws EvsException
    {
        try
        {
            _execute( response );
            _runAfterInterceptors( response );
        }
        catch ( EvsException e )
        {
            _runErrorInterceptors( response, e );
            throw e;
        }
    }
    
    private void _runErrorInterceptors( HttpServletResponse response, EvsException e )
    {
        for ( RequestInterceptor interceptor : _requestInterceptors )
            interceptor.error( _request, response, e );
    }

    protected Class<?> _getExpectedEntityClass() throws EvsException
    {
        if ( _expectedEntityClass == null )
        {
            String requestPath = extractPath( _request );
            _expectedEntityClass = _extractEntityClass( requestPath );
        }
        
        return _expectedEntityClass;    
    }
    
    private String _extractEntityId( String requestPath )
    {
        int slashIndex = requestPath.indexOf( "/" );
        
        if ( slashIndex < 0 )
            return null;
        
        return requestPath.substring( slashIndex + 1 );
    }

    private Class<?> _extractEntityClass( String requestPath ) throws EvsException
    {
        int slashIndex = requestPath.indexOf( "/" );
        
        String entityPath = slashIndex > 0 ? requestPath.substring( 0, slashIndex ) : requestPath;

        Class<?> entityClass = _entityMap.getEntityForPath( entityPath );
        
        if ( entityClass == null )
            throw new EvsException( HttpServletResponse.SC_NOT_FOUND, 
                            "No entity class with name " + entityPath + " could be found!" );
        
        return entityClass;
    }
    
    public static String extractPath( HttpServletRequest request )
    {
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String servletPath = request.getServletPath();
        
        assert uri.startsWith( contextPath );

        // +1 accounts for the '/' after the context path
        return uri.substring( contextPath.length() + servletPath.length() + 1 );
    }

    private MediaType _extractMediaType( HttpServletRequest request ) throws EvsException
    {
        String mime = request.getContentType();
        
        if ( "application/json".equalsIgnoreCase( mime ) )
            return MediaType.JSON;
        
        if ( "application/xml".equalsIgnoreCase( mime ) || 
             "text/xml".equalsIgnoreCase( mime ) )
            return MediaType.XML;
        
        if ( !this.allowUnknownMediaType() )
            throw new EvsException( HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, 
                            "Media type not supported: " + mime );
        
        return null;
    }
    
    private void _runBeforeInterceptors( HttpServletRequest request ) throws EvsException
    {
        _requestInterceptors = new ArrayList<RequestInterceptor>( _requestInterceptorClasses.size() );
        
        for ( Class<?> interceptorClass : _requestInterceptorClasses )
        {
            try
            {
                _requestInterceptors.add( (RequestInterceptor) interceptorClass.newInstance() );
            }
            catch ( InstantiationException e )
            {
                e.printStackTrace();
            }
            catch ( IllegalAccessException e )
            {
                e.printStackTrace();
            }
        }
        
        for ( RequestInterceptor interceptor : _requestInterceptors )
            interceptor.before( request );
    }
    
    private void _runAfterInterceptors( HttpServletResponse response ) throws EvsException
    {
        for ( RequestInterceptor interceptor : _requestInterceptors )
            interceptor.after( response );
    }
}
